package com.partsoft.dits;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Properties;
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import com.partsoft.dits.ConfigurableBean;
import com.partsoft.dits.ContextConfigurableBean;
import com.partsoft.dits.utils.Dom4jUtils;

/**
 * 可配置的上下文描述管理器<br/>
 * 用于实现从Spring上下文配置加载描述符对象。
 * 并且可以实现保存上下文配置加载对象至Spring配置文件中。
 * @param <E> 必须实现{@link EdgeDescriptor}接口
 */
@SuppressWarnings("unchecked")
public abstract class AbstractConfigurableDescriptorManager<E extends EdgeDescriptor> extends AbstractDescritptorManager<E> implements ConfigurableBean, DescriptorDepends<E> {
	
	private ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
	
	private ContextConfigurableBean configurableDelegate = new ContextConfigurableBean() {
		@Override
		protected void loadContextConfiguredBeans(ConfigurableApplicationContext ctx) {
			loadConfiguredDescriptors(ctx);
		}

		@Override
		protected void unLoadContextConfiguredBeans() {
			unLoadConfiguredDescriptors();
		}
	};
	
	public AbstractConfigurableDescriptorManager() {}
	
	public AbstractConfigurableDescriptorManager(String contextConfigLocation) {
		setContextProfileLocation(contextConfigLocation);
	}
	
	public AbstractConfigurableDescriptorManager(String contextConfigLocation, boolean createNoExists) {
		setContextProfileLocation(contextConfigLocation);
		setCreateProfileNoExists(createNoExists);
	}
	
	public ResourceLoader getResourceLoader() {
		return resourceLoader;
	}
	
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	/**
	 * 配合{@link #convertConfiguredBean(String, Object)}使用
	 * @return
	 */
	protected Class<?> resolveConfiguredBeanClass() {
		return getSupportClass();
	}
	
	private void loadConfiguredDescriptors(ConfigurableApplicationContext pcContext) {
		String[] beanIds = pcContext.getBeanNamesForType(resolveConfiguredBeanClass());
		for (String beanId : beanIds) {
			Object rawObject = pcContext.getBean(beanId);
			E descriptor = convertConfiguredBean(beanId, rawObject);
			beforeLoadConfiguredDescriptor(descriptor);
			super.addDescriptor((E) descriptor);
			postLoadConfiguredDescriptor(descriptor);
		}
	}
	
	
	private void unLoadConfiguredDescriptors() {
		for (E desc : new ArrayList<E>(getDescriptorMap().values())) {
			beforeUnLoadConfiguredDescriptor(desc);
			desc = super.removeDescriptor(resolveDescriptorIdentifier(desc));
			postUnLoadConfiguredDescriptor(desc);
		}
	}
	
	public void finiContext() {
		synchronized (this) {
			configurableDelegate.finiContext();
		}
	}
	
	protected void assertNoInit() {
		Assert.isTrue(!isAlreadyInit(), "已经初始化配置上下文。");
	}
	
	protected void assertAlreadyInit() {
		Assert.isTrue(isAlreadyInit(), "未初始化配置上下文。");
	}

	public void initContext() {
		synchronized (this) {
			if (isAlreadyInit()) finiContext();
			try {
				configurableDelegate.initContext();
			} catch (Throwable e) {
				throw new IllegalArgumentException(e.getMessage(), e);
			}
		}
	}
	
	
	
	@Override
	public final E addDescriptor(E descriptor) {
		assertAlreadyInit();
		validateDescriptor(descriptor);
		String publishBeanId = resolveDescriptorIdentifier(descriptor);
		Assert.hasText(publishBeanId, "不能获取ID");
		Dom4jUtils.validateSpringID(publishBeanId);
		descriptor = (E) descriptor.clone();
		synchronized (this) {
			Assert.isTrue(!hasDescriptor(publishBeanId),  String.format("描述符编号(%s)已存在。", publishBeanId));
			setupDescriptorIdentifier(descriptor, publishBeanId);
			Properties beanProps = beforeAddDescriptor(descriptor);
			try {
				configurableDelegate.appendConfiguredBean(publishBeanId, resolveConfiguredBeanClass().getName(), beanProps);
			} catch (IOException e) {
				throw new IllegalStateException(String.format("%s", e.getMessage()), e);
			}
			E result = super.addDescriptor(descriptor);
			postAddDescriptor(descriptor);
			return result;
		}
	}
	
	@Override
	public final E removeDescriptor(E descriptor) {
		assertAlreadyInit();
		Assert.notNull(descriptor);
		String publishBeanId = resolveDescriptorIdentifier(descriptor);
		return this.removeDescriptor(publishBeanId);
	}
	
	@Override
	public E removeDescriptor(String publishBeanId) {
		assertAlreadyInit();
		synchronized (this) {
			if (!hasDescriptor(publishBeanId)) return null;
			E descriptor = getDescriptor(publishBeanId);
			beforeRemoveDescriptor(descriptor);
			try {
				configurableDelegate.removeConfiguredBean(publishBeanId);
			} catch (IOException e) {
				throw new IllegalStateException(String.format("%s", e.getMessage()), e);
			}
			E result = super.removeDescriptor(publishBeanId);
			postRemoveDescriptor(descriptor);
			return result;
		}
	}

	public void setOwnerContext(ApplicationContext ownerContext) {
		configurableDelegate.setOwnerContext(ownerContext);
	}

	public ApplicationContext getOwnerContext() {
		return configurableDelegate.getOwnerContext();
	}

	/**
	 * 查看{@link ContextConfigurableBean#setProfileCanBeWrite(boolean)}实现
	 */
	public void setProfileCanBeWrite(boolean profileCanBeWrite) {
		configurableDelegate.setProfileCanBeWrite(profileCanBeWrite);
	}

	/**
	 * 查看{@link ContextConfigurableBean#setProfileCanBeWrite(boolean)}实现
	 */
	public void setCreateProfileNoExists(boolean createNoExists) {
		configurableDelegate.setCreateProfileNoExists(createNoExists);
	}

	public String getContextProfileLocation() {
		return configurableDelegate.getContextProfileLocation();
	}
	
	/**
	 * 查看{@link ContextConfigurableBean#setContextProfileLocation(String)}实现
	 */
	public void setContextProfileLocation(String contextConfigLocation) {
		configurableDelegate.setContextProfileLocation(contextConfigLocation);
	}

	public boolean isProfileCanBeWrite() {
		return configurableDelegate.isProfileCanBeWrite();
	}

	public boolean isCreateProfileNoExists() {
		return configurableDelegate.isCreateProfileNoExists();
	}

	public boolean isAlreadyInit() {
		return configurableDelegate.isAlreadyInit();
	}

	public void setApplicationContext(ApplicationContext ownerContext)
			throws BeansException {
		setOwnerContext(ownerContext);
	}

	public void onApplicationEvent(ApplicationEvent event) {
		if (getOwnerContext() == null)
			return;
		if (!ObjectUtils.nullSafeEquals(getOwnerContext(), event.getSource()))
			return;
		if (event instanceof ContextRefreshedEvent) {
			initContext();
		} else if (event instanceof ContextStoppedEvent) {
			finiContext();
		}
	}

	/**
	 * 移除之前的动作
	 * @param descriptor
	 */
	protected abstract void beforeRemoveDescriptor(E descriptor);
	
	/**
	 * 提交移除之后的动作
	 * @param descriptor
	 */
	protected abstract void postRemoveDescriptor(E descriptor);
	
	/**
	 * 装载一个配置的描述符之前的动作(缺省动作为,加载转换脚本文件)
	 */
	protected  abstract void beforeLoadConfiguredDescriptor(E descriptor);
	
	/**
	 * 装载配置描述符之后的动作
	 */
	protected void postLoadConfiguredDescriptor(E descriptor){}
	
	protected void beforeUnLoadConfiguredDescriptor(E descriptor) {}
	
	protected void postUnLoadConfiguredDescriptor(E descriptor) {}
	
	/**
	 * 添加之前的动作,返回配置属性
	 */
	protected abstract Properties beforeAddDescriptor(E descriptor);
	
	/**
	 * 设置描述符编号
	 * @param descriptor
	 * @param id
	 */
	protected abstract void setupDescriptorIdentifier(E descriptor, String id);
	
	/**
	 * 提交添加之后的动作
	 */
	protected void postAddDescriptor(E descriptor) {}

	/**
	 * 校验描述符是否符合属性
	 */
	protected abstract void validateDescriptor(E descriptor);
	
	/**
	 * 转换配置的BEAN为描述符
	 * @param rawObject
	 */
	protected E convertConfiguredBean(String beanId, Object rawObject) {
		return (E) rawObject;
	}
	
	public Set<Class<? extends EdgeDescriptor>> getDependClasses() {
		return Collections.EMPTY_SET;
	}
	
	public boolean dependDescriptor(EdgeDescriptor descriptor) {
		return false;
	}
	
	public E[] getDescriptorByDepend(EdgeDescriptor descriptor) {
		return (E[]) Array.newInstance(getSupportClass(), 0);
	}

}
